﻿using System;
using System.Collections.Generic;
using System.Linq;
using Beaker.Injection.Desktop.SimpleInjector;
using Beaker.Specs;
using FluentAssertions;
using Microsoft.Practices.ServiceLocation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NCrunch.Framework;

namespace Beaker.Injection.Desktop.Specs
{
	[TestClass]
	public sealed class InjectionManagerSpecs
		: SpecsFor<InjectionManager>
	{
		[TestInitialize]
		public void Initialize()
		{
			// make sure the reference is not optimized away by compiler
			new SimpleInjectorInjectionProvider();
		}

		[TestMethod]
		public void GivenARegisteredService_WhenGettingInstance_ThenShouldReturnInstanceOfService()
		{
			IQueryContainer container = Subject.SearchAndRegisterModules();
			var result = container.GetInstance<IRegisteredService>();
			result.Should().NotBeNull();
		}

		[TestMethod]
		public void GivenAUnregisteredService_WhenGettingInstance_ThenShouldThrowActivationException()
		{
			IQueryContainer container = Subject.SearchAndRegisterModules();
			Action act = () => container.GetInstance<IUnregisteredService>();
			act.ShouldThrow<ActivationException>();
		}

		/// <summary>
		/// Check that the service locator is set to the container
		/// </summary>
		[TestMethod]
		public void GivenContainerWithRegisteredTypes_WhenQueryingTypeThruLocator_ThenReturnsInstanceOfRegisteredType()
		{
			//! given a container with a register type (IRegisterService)
			Subject.SearchAndRegisterModules();

			//! when querying type thru the locator
			ServiceLocator.IsLocationProviderSet.Should().BeTrue("The locator should be set after the registration phase.");
			var result = ServiceLocator.Current.GetInstance<IRegisteredService>();

			//! then it should return an instance of that registered type.
			result.Should().NotBeNull("The locator should also return instances of types registerd on the container.");
		}

		[TestMethod]
		public void T()
		{
			IQueryContainer container = Subject.SearchAndRegisterModules();

			IEnumerable<ISomeProvider> providers = container.GetAllInstances<ISomeProvider>();

			providers.Should().Contain(p => p is SomeProvider1);
			providers.Should().Contain(p => p is SomeProvider2);
		}

		private static Action _setInitCalled;
		private static Action _setCleanupCalled;

		[TestMethod]
		[Isolated] // isolated because it uses the static properties for this test
		public void MyTestMethod()
		{
			bool initCalled = false;
			bool cleanupCalled = false;

			_setInitCalled = () => initCalled = true;
			_setCleanupCalled = () => cleanupCalled = true;

			IQueryContainer container = Subject.SearchAndRegisterModules();

			initCalled.Should().BeFalse();
			cleanupCalled.Should().BeFalse();

			Subject.InitializeModules(container);
			initCalled.Should().BeTrue();
			cleanupCalled.Should().BeFalse();

			Subject.CleanupModules(container);
			cleanupCalled.Should().BeTrue();
		}

		[TestMethod]
		public void CheckThatTheIFactoryAutomaticlyReturnsFactoryForType()
		{
			IQueryContainer container = Subject.SearchAndRegisterModules();

			var instance = container.GetInstance<IRegisteredService>();
			var factory = container.GetInstance<IFactory<IRegisteredService>>();
			IRegisteredService result = factory.Create();

			result.Should().NotBeNull();
		}

		/// <summary>
		/// Check that when requesting for the base, the configured specific interface is used.
		/// </summary>
		[TestMethod]
		public void CheckThatWhenRequestingForTheBaseInterfaceTheConfiguredSpecificInterfaceIsUsed()
		{
			IQueryContainer container = Subject.SearchAndRegisterModules();

			var baseInterface = container.GetInstance<IBase>();
			baseInterface.Should().BeOfType<Specific>();
		}

		[TestMethod]
		public void CheckThatSingletonsAreCorrectlyRegistersWhenUsingTheAnotherMethods()
		{
			IQueryContainer container = Subject.SearchAndRegisterModules();

			List<ISingleton> allSingletons1 = container.GetAllInstances<ISingleton>().ToList();
			List<ISingleton> allSingletons2 = container.GetAllInstances<ISingleton>().ToList();

			allSingletons1.OfType<Singleton1>().Single().Should().BeSameAs(allSingletons2.OfType<Singleton1>().Single());
			allSingletons1.OfType<Singleton2>().Single().Should().BeSameAs(allSingletons2.OfType<Singleton2>().Single());
		}

		[TestMethod]
		public void CheckThatSingletonImplementation()
		{
			IQueryContainer container = Subject.SearchAndRegisterModules();

			var s1 = container.GetInstance<ISingleton3>();
			var s2 = container.GetInstance<ISingleton3>();
			var s3 = container.GetInstance<Singleton3>();
			var s4 = container.GetInstance<Singleton3>();

			s3.Should().BeSameAs(s4);
			s1.Should().BeSameAs(s3);
			s2.Should().BeSameAs(s4);
		}

		internal sealed class TestModule
			: IInjectionModule
		{
			public void Register(IContainerBuilder builder)
			{
				builder.Register<IRegisteredService, RegisteredService>();
				builder.RegisterAnother<ISomeProvider, SomeProvider1>();
				builder.RegisterAnother<IInitialization, InitTest>();
				builder.RegisterAnother<ICleanup, CleanTest>();
				builder.RegisterAnotherSingleton<ISingleton, Singleton1>();
				builder.RegisterAnotherSingleton<ISingleton, Singleton2>();
				builder.RegisterSingleton<Singleton3>();
				builder.Register<ISingleton3, Singleton3>();

				builder.Register<ISpecific, Specific>();
				builder.RegisterDefault<IBase, ISpecific>();
			}
		}

		internal sealed class Test2Module
			: IInjectionModule
		{
			public void Register(IContainerBuilder builder)
			{
				builder.RegisterAnother<ISomeProvider, SomeProvider2>();
			}
		}

		private sealed class InitTest
			: IInitialization
		{
			public void Initialize()
			{
				if (_setInitCalled != null)
					_setInitCalled();
			}
		}

		private sealed class CleanTest
			: ICleanup
		{
			public void Cleanup()
			{
				if (_setCleanupCalled != null)
					_setCleanupCalled();
			}
		}

		private sealed class RegisteredService
			: IRegisteredService
		{
		}

		private sealed class UnregisteredService
			: IUnregisteredService
		{
		}

		private sealed class SomeProvider1
			: ISomeProvider
		{
		}

		private sealed class SomeProvider2
			: ISomeProvider
		{
		}

		private sealed class Specific
			: ISpecific
		{
		}

		private sealed class Singleton1
			: ISingleton
		{
		}

		private sealed class Singleton2
			: ISingleton
		{
		}

		private sealed class Singleton3
			: ISingleton3
		{
		}

	}

	public interface IRegisteredService
	{
	}

	public interface IUnregisteredService
	{
	}

	public interface ISomeProvider
	{
	}

	public interface IBase
	{
	}

	public interface ISpecific
		: IBase
	{
	}

	public interface ISingleton
	{
	}

	public interface ISingleton3
	{
	}

}
